home *** CD-ROM | disk | FTP | other *** search
/ HPAVC / HPAVC CD-ROM.iso / pc / MASM.ZIP / SAMPLES / TSR / HANDLERS.AS$ / HANDLERS.bin
Encoding:
Text File  |  1991-03-09  |  50.9 KB  |  1,290 lines

  1.         .MODEL  small, pascal, os_dos
  2.  
  3. ; Prototypes for internal procedures
  4. Activate        PROTO  NEAR
  5. CheckRequest    PROTO  NEAR
  6. CheckDos        PROTO  NEAR
  7. CheckHardware   PROTO  NEAR
  8. GetDosFlags     PROTO  NEAR
  9.  
  10.         INCLUDE tsr.inc
  11.  
  12.         .CODE
  13.  
  14. ; Stack buffer used by TSR. Size is determined by constant STACK_SIZ,
  15. ; declared in TSR.INC file. NewStack points to top of stack.
  16.  
  17.          EVEN
  18.          BYTE   STACK_SIZ DUP(?)        ; Stack buffer
  19. NewStack LABEL BYTE                     ; Pointer to top of stack
  20.  
  21. ; Structures for interrupt handlers or "interrupt service routines."
  22. ; The following handlers are replaced during installation. Such routines
  23. ; usually set a flag to indicate they are active, optionally do some
  24. ; processing (such as detecting a hot key), call the old system interrupt
  25. ; routine, and when finished clear the active flag.
  26.  
  27. HandArray       LABEL   BYTE                    ; Array of handler structures
  28. ;                Num   Flag     OldHand  NewHand
  29. intClock  INTR  < 8h,  FALSE,   NULL,    Clock>
  30. intKeybrd INTR  < 9h,  FALSE,   NULL,    Keybrd>
  31. intVideo  INTR  <10h,  FALSE,   NULL,    Video>
  32. intDiskIO INTR  <13h,  FALSE,   NULL,    DiskIO>
  33. intMisc   INTR  <15h,  FALSE,   NULL,    SkipMiscServ>
  34. intIdle   INTR  <28h,  FALSE,   NULL,    Idle>
  35. intMultex INTR  <2Fh,  FALSE,   NULL,    Multiplex>
  36.  
  37. CHAND   EQU     ($ - HandArray) / (SIZEOF INTR) ; Number of handlers in array
  38.  
  39. ; Interrupt trap routines. These interrupt routines are set up
  40. ; temporarily to trap keyboard break errors and critical errors
  41. ; while the TSR is active. When the TSR finishes its tasks, it
  42. ; restores the old interrupts before returning.
  43.  
  44. TrapArray       LABEL   BYTE                    ; Array of trap structures
  45. ;                Num   Flag     OldHand  NewHand
  46. intCtrlBk INTR  <1Bh,  FALSE,   NULL,    CtrlBreak>
  47. intCtrlC  INTR  <23h,  FALSE,   NULL,    CtrlC>
  48. intCritEr INTR  <24h,  FALSE,   NULL,    CritError>
  49.  
  50. CTRAP   EQU     ($ - TrapArray) / (SIZEOF INTR) ; Number of traps in array
  51.  
  52. ; Address of application's stack. Before calling the main body of the TSR,
  53. ; the Activate procedure stores the application's stack address, then resets
  54. ; SS:SP to point to LABEL NewStack (see above). This gives the TSR its own
  55. ; stack space without making demands on the current stack. Activate restores
  56. ; the application's stack before returning.
  57.  
  58. OldStackAddr    FPVOID  ?               ; SS:SP pointer to application stack
  59.  
  60. ; The TSR must set up its own disk transfer area if it calls DOS functions
  61. ; that use the DTA (see Section 19.5.3 of the Programmer's Guide). DTA_SIZ
  62. ; is defined in the TSR.INC include file.
  63.  
  64.                 IFDEF   DTA_SIZ
  65. OldDtaAddr      FPVOID  ?               ; Address of application's DTA
  66. DtaBuff         BYTE    DTA_SIZ DUP(?)  ; DTA buffer
  67.                 ENDIF
  68.  
  69. ; Multiplex data. STR_LEN is defined in the TSR.INC include file
  70.  
  71. IDnumber        BYTE    0               ; TSR's identity number
  72. IDstring        BYTE    STR_LEN DUP (0) ; Copy of identifier string
  73. IDstrlen        WORD    ?               ; Length of identifier string
  74. ShareAddr       FPVOID  ?               ; Address of shared memory
  75.  
  76. ; Miscellaneous data
  77.  
  78. TsrRequestFlag  BYTE    FALSE           ; Flag set when hot key is pressed
  79. TsrActiveFlag   BYTE    FALSE           ; Flag set when TSR executes
  80. BreakCheckFlag  BYTE    ?               ; Break-checking status of application
  81. TsrPspSeg       WORD    ?               ; Segment address of PSP
  82. TsrAddr         FPVOID  ?               ; Pointer to main part of TSR
  83. CritErrAddr     FPVOID  ?               ; Pointer to MS-DOS critical error flag
  84. InDosAddr       FPVOID  ?               ; Pointer to MS-DOS InDos flag
  85.  
  86. ; Scan and shift codes for hot key. Install procedure initializes
  87. ; HotScan, HotShift, and HotMask during installation.
  88.  
  89. HotScan         BYTE    ?               ; Scan code hot key
  90. HotShift        BYTE    ?               ; Shift value of hot key
  91. HotMask         BYTE    ?               ; Mask unwanted shift values
  92.  
  93. Version         LABEL   WORD            ; DOS version number
  94. minor           BYTE    ?
  95. major           BYTE    ?
  96.  
  97. ; Timer data, used when the TSR is activated at a preset time instead
  98. ; of activated from the keyboard. The following variables serve the
  99. ; same purposes as the counter variables used in the ALARM.ASM program
  100. ; presented in Section 19.3 of the Programmer's Guide. Refer to the
  101. ; header comments in the Install procedure for an explanation of how
  102. ; to set up a time-activated TSR.
  103.  
  104. Tick91          BYTE    91              ; Measures 91 timer ticks (5 seconds)
  105. CountDown       WORD    0               ; Counts 5-second intervals
  106.  
  107.  
  108.  
  109. ;* Clock - Interrupt handler for Interrupt 08 (timer). Executes at each
  110. ;* timer interrupt; these occur an average of 18.2 times per second. Clock
  111. ;* first allows the original timer service routine to execute. It then
  112. ;* checks the flag TsrRequestFlag maintained either by the keyboard handler
  113. ;* (if keyboard activated) or by this procedure (if time activated). If
  114. ;* TsrRequestFlag = TRUE and system is okay, Clock invokes the TSR by
  115. ;* calling the Activate procedure. Uses an active flag to prevent the
  116. ;* Clock procedure from being reentered while executing.
  117. ;*
  118. ;* Uses:   intClock, TsrActiveFlag, CountDown
  119. ;*
  120. ;* Params: None
  121. ;*
  122. ;* Return: None
  123.  
  124. Clock   PROC    FAR
  125.  
  126.         pushf                           ; Simulate interrupt by pushing flags,
  127.         call    cs:intClock.OldHand     ;   far calling orig Int 08 routine
  128.  
  129.         .IF     cs:intClock.Flag == FALSE ; If not already in this handler:
  130.         mov     cs:intClock.Flag, TRUE  ; Set active flag
  131.  
  132.         sti                             ; Interrupts are okay
  133.         push    ds                      ; Save application's DS
  134.         push    cs
  135.         pop     ds                      ; Set DS to resident code segment
  136.         ASSUME  ds:@code
  137.  
  138.         INVOKE  CheckRequest            ; Check conditions
  139.         .IF     !carry?                 ; If TSR requested and safe,
  140.         mov     TsrActiveFlag, TRUE     ;   activate TSR
  141.         INVOKE  Activate
  142.         mov     TsrActiveFlag, FALSE
  143.         .ENDIF                          ; End carry flag check
  144.  
  145.         cmp     CountDown, 0            ; If CountDown = 0, TSR is not time
  146.         je      ticked                  ;   activated or has already executed
  147.         dec     Tick91                  ; Else count down 91 timer ticks
  148.         jnz     ticked                  ; If 91 ticks have not elapsed, exit
  149.         mov     Tick91, 91              ; Else reset secondary counter and
  150.         dec     CountDown               ;   subract one 5-second interval
  151.         ja      ticked                  ; If counter not yet drained, exit
  152.         mov     TsrRequestFlag, TRUE    ; Else raise request flag
  153. ticked:
  154.         mov     intClock.Flag, FALSE    ; Clear active flag
  155.         pop     ds                      ; Recover application's DS
  156.         ASSUME  ds:NOTHING
  157.  
  158.         .ENDIF                          ; End in-handler check
  159.         iret
  160.  
  161. Clock   ENDP
  162.  
  163.  
  164. ;* Keybrd - Interrupt handler for Interrupt 09 (keyboard).
  165. ;*
  166. ;* IBM PC/AT and compatibles:
  167. ;*      Gets the scan code of the current keystroke from port 60h. Then
  168. ;*      compares the scan code and shift state to the hot key. If they
  169. ;*      match, sets TsrRequestFlag to signal the handlers Clock and Idle
  170. ;*      that the TSR is requested.
  171. ;* 
  172. ;* IBM PS/2 series:
  173. ;*      Only the instructions at KeybrdMonitor (see below) are installed
  174. ;*      as Interrupt 09 handler, since above method should not be used to
  175. ;*      determine current keystroke in IBM PS/2 series. In this case, the
  176. ;*      Interrupt 15h handler MiscServ takes care of checking the scan codes
  177. ;*      and setting the request flag when the hot key is pressed.
  178. ;*
  179. ;* Time-activated TSRs:
  180. ;*      If the TSR is activated by time instead of by a hotkey, KeybrdMonitor
  181. ;*      serves as the Interrupt 09 handler for both PC/AT and PS/2 systems.
  182. ;*
  183. ;* Uses:   intKeybrd, TsrRequestFlag
  184. ;* 
  185. ;* Params: None
  186. ;*
  187. ;* Return: None
  188.  
  189. Keybrd  PROC    FAR
  190.  
  191.         sti                             ; Interrupts are okay
  192.         push    ax                      ; Save AX register
  193.         in      al, 60h                 ; AL = scan code of current key
  194.         call    CheckHotKey             ; Check for hot key
  195.         .IF     !carry?                 ; If not hot key:
  196.  
  197. ; Hot key pressed. Reset the keyboard to throw away keystroke.
  198.  
  199.         cli                             ; Disable interrupts while resetting
  200.         in      al, 61h                 ; Get current port 61h state
  201.         or      al, 10000000y           ; Turn on bit 7 to signal clear keybrd
  202.         out     61h, al                 ; Send to port
  203.         and     al, 01111111y           ; Turn off bit 7 to signal break
  204.         out     61h, al                 ; Send to port
  205.         mov     al, 20h                 ; Reset interrupt controller
  206.         out     20h, al
  207.         sti                             ; Reenable interrupts
  208.  
  209.         pop     ax                      ; Recover AX
  210.         mov     cs:TsrRequestFlag, TRUE ; Raise request flag
  211.         iret                            ; Exit interrupt handler
  212.         .ENDIF                          ; End hot-key check
  213.  
  214. ; No hot key was pressed, so let normal Int 09 service routine take over
  215.  
  216.         pop     ax                      ; Recover AX and fall through
  217.         cli                             ; Interrupts cleared for service
  218.  
  219. KeybrdMonitor LABEL FAR                 ; Installed as Int 09 handler for
  220.                                         ;   PS/2 or for time-activated TSR
  221.         mov     cs:intKeybrd.Flag, TRUE ; Signal that interrupt is busy
  222.         pushf                           ; Simulate interrupt by pushing flags,
  223.         call    cs:intKeybrd.OldHand    ;   far calling old Int 09 routine
  224.         mov     cs:intKeybrd.Flag, FALSE
  225.         iret
  226.  
  227. Keybrd  ENDP
  228.  
  229.  
  230. ;* Video - Interrupt handler for Interrupt 10h (video). Allows the original
  231. ;* video service routine to execute. Maintains an active flag to prevent
  232. ;* the TSR from being called while Interrupt 10h is executing.
  233. ;*
  234. ;* Uses:   intVideo
  235. ;*
  236. ;* Params: Registers passed to Interrupt 10h
  237. ;*
  238. ;* Return: Registers returned by Interrupt 10h
  239.  
  240. Video   PROC    FAR
  241.  
  242.         mov     cs:intVideo.Flag, TRUE  ; Set active flag
  243.         pushf                           ; Simulate interrupt by pushing flags,
  244.         call    cs:intVideo.OldHand     ;   far calling old Int 10h routine
  245.         mov     cs:intVideo.Flag, FALSE ; Clear active flag
  246.         iret
  247.  
  248. Video   ENDP
  249.  
  250.  
  251. ;* DiskIO - Interrupt handler for Interrupt 13h (disk I/O). Allows the
  252. ;* original disk I/O service routine to execute. Maintains an active flag
  253. ;* to prevent the TSR from being called while Interrupt 13h is executing.
  254. ;*
  255. ;* Uses:   intDiskIO
  256. ;*
  257. ;* Params: Registers passed to Interrupt 13h
  258. ;*
  259. ;* Return: Registers and the carry flag returned by Interrupt 13h
  260.  
  261. DiskIO  PROC    FAR
  262.  
  263.         mov     cs:intDiskIO.Flag, TRUE ; Set active flag
  264.         pushf                           ; Simulate interrupt by pushing flags,
  265.         call    cs:intDiskIO.OldHand    ;   far calling old Int 13h routine
  266.         mov     cs:intDiskIO.Flag, FALSE; Clear active flag without
  267.                                         ;   disturbing flags register
  268.         sti                             ; Enable interrupts
  269.         ret     2                       ; Simulate IRET without popping flags
  270.                                         ;   (since services use carry flag)
  271. DiskIO  ENDP
  272.  
  273.  
  274. ;* MiscServ - Interrupt handler for Interrupt 15h (Miscellaneous System
  275. ;* Services).
  276. ;*
  277. ;* IBM PC/AT and compatibles:
  278. ;*     Stub at SkipMiscServ is used as handler, bypassing all calls to
  279. ;*     Interrupt 15h. Keypresses are checked by Keybrd (Int 09 handler).
  280. ;* 
  281. ;* IBM PS/2 series:
  282. ;*     This procedure handles calls to Interrupt 15h, searching for
  283. ;*     Function 4Fh (Keyboard Intercept Service). When AH = 4Fh, gets
  284. ;*     scan code of current keystroke in AL register. Then compares the
  285. ;*     scan code and shift state to the hot key. If they match, sets
  286. ;*     TsrRequestFlag to signal the handlers Clock and Idle that the
  287. ;*     TSR is requested.
  288. ;*
  289. ;* Uses:   intMisc, TsrRequestFlag
  290. ;*
  291. ;* Params: Registers passed to Interrupt 15h
  292. ;*
  293. ;* Return: Registers returned by Interrupt 15h
  294.  
  295. MiscServ PROC   FAR
  296.  
  297.         sti                             ; Interrupts okay
  298.         .IF     ah == 4Fh               ; If Keyboard Intercept Service,
  299.         push    ax                      ;   preserve AX,
  300.         call    CheckHotKey             ;   check for hot key
  301.         pop     ax
  302.         .IF     !carry?                 ; If hot key,
  303.         mov     cs:TsrRequestFlag, TRUE ;   raise request flag,
  304.         clc                             ;   signal BIOS not to process the key,
  305.         ret     2                       ;   simulate IRET without popping flags
  306.         .ENDIF                          ; End carry flag check
  307.         .ENDIF                          ; End Keyboard Intercept check
  308.  
  309.         cli                             ; Disable interrupts and fall through
  310.  
  311. SkipMiscServ LABEL FAR                  ; Interrupt 15h handler if PC/AT
  312.  
  313.         jmp     cs:intMisc.OldHand
  314.  
  315. MiscServ ENDP
  316.  
  317.  
  318. ;* CtrlBreak - Interrupt trap for Interrupt 1Bh (CTRL+BREAK Handler).
  319. ;* Disables CTRL+BREAK processing.
  320. ;*
  321. ;* Params: None
  322. ;*
  323. ;* Return: None
  324.  
  325. CtrlBreak PROC  FAR
  326.  
  327.         iret
  328.  
  329. CtrlBreak ENDP
  330.  
  331.  
  332. ;* CtrlC - Interrupt trap for Interrupt 23h (CTRL+C Handler).
  333. ;* Disables CTRL+C processing.
  334. ;*
  335. ;* Params: None
  336. ;*
  337. ;* Return: None
  338.  
  339. CtrlC   PROC    FAR
  340.  
  341.         iret
  342.  
  343. CtrlC   ENDP
  344.  
  345.  
  346. ;* CritError - Interrupt trap for Interrupt 24h (Critical Error Handler).
  347. ;* Disables critical error processing.
  348. ;*
  349. ;* Params: None
  350. ;*
  351. ;* Return: AL = Stop code 0 or 3
  352.  
  353. CritError PROC  FAR
  354.  
  355.         sti
  356.         sub     al, al                  ; Assume DOS 2.x
  357.                                         ; Set AL = 0 for ignore error
  358.         .IF     cs:major != 2           ; If DOS 3.x, set AL = 3
  359.         mov     al, 3                   ; DOS call fails
  360.         .ENDIF
  361.  
  362.         iret
  363.  
  364. CritError ENDP
  365.  
  366.  
  367. ;* Idle - Interrupt handler for Interrupt 28h (DOS Idle). Allows the
  368. ;* original Interrupt 28h service routine to execute. Then checks the
  369. ;* request flag TsrRequestFlag maintained either by the keyboard handler
  370. ;* (keyboard-activated TSR) or by the timer handler (time-activated TSR).
  371. ;* See header comments above for Clock, Keybrd, and MiscServ procedures.
  372. ;*
  373. ;* If TsrRequestFlag = TRUE and system is in interruptable state, Idle
  374. ;* invokes the TSR by calling the Activate procedure. Uses an active flag
  375. ;* to prevent the Idle procedure from being reentered while executing.
  376. ;*
  377. ;* Uses:   intIdle and TsrActiveFlag
  378. ;*
  379. ;* Params: None
  380. ;*
  381. ;* Return: None
  382.  
  383. Idle    PROC    FAR
  384.  
  385.         pushf                           ; Simulate interrupt by pushing flags,
  386.         call    cs:intIdle.OldHand      ;   far-calling old Int 28h routine
  387.  
  388.         .IF     cs:intIdle.Flag == FALSE; If not already in this handler,
  389.         mov     cs:intIdle.Flag, TRUE   ;   set active flag
  390.  
  391.         sti                             ; Interrupts are okay
  392.         push    ds                      ; Save application's DS
  393.         push    cs
  394.         pop     ds                      ; Set DS to resident code segment
  395.         ASSUME  ds:@code
  396.  
  397.         INVOKE  CheckRequest            ; Check conditions
  398.         .IF     !carry?                 ; If TSR requested and safe,
  399.         mov     TsrActiveFlag, TRUE     ;   activate TSR
  400.         INVOKE  Activate
  401.         mov     TsrActiveFlag, FALSE
  402.         .ENDIF                          ; End carry flag check
  403.  
  404.         mov     intIdle.Flag, FALSE     ; Clear active flag
  405.         pop     ds                      ; Recover application's DS
  406.         .ENDIF                          ; End in-handler check
  407.  
  408.         iret
  409.  
  410. Idle    ENDP
  411.  
  412.  
  413. ;* Multiplex - Handler for Interrupt 2Fh (Multiplex Interrupt). Checks
  414. ;* AH for this TSR's identity number. If no match (indicating call is
  415. ;* not intended for this TSR), Multiplex passes control to the previous
  416. ;* Interrupt 2Fh handler.
  417. ;*
  418. ;* Params: AH = Handler identity number
  419. ;*         AL = Function number 0-2
  420. ;*
  421. ;* Return: AL    = 0FFh (function 0)
  422. ;*         ES:DI = Pointer to identifier string (function 0)
  423. ;*         ES:DI = Pointer to resident PSP segment (function 1)
  424. ;*         ES:DI = Pointer to shared memory (function 2)
  425.  
  426. Multiplex PROC  FAR
  427.  
  428.         .IF     ah != cs:IDnumber       ; If this handler not requested,
  429.         jmp     cs:intMultex.OldHand    ;   pass control to old Int 2Fh
  430.         .ENDIF                          ;   handler
  431.  
  432.         .IF     al == 0                 ; If function 0 (verify presence),
  433.         mov     al, 0FFh                ;   AL = 0FFh,
  434.         push    cs                      ;   ES = resident code segment
  435.         pop     es
  436.         mov     di, OFFSET IDstring     ;   DI = offset of identifier string
  437.  
  438.         .ELSEIF al == 1                 ; If function 1 (get PSP address),
  439.         mov     es, cs:TsrPspSeg        ;   ES:DI = far address of resident PSP
  440.         sub     di, di
  441.  
  442.         .ELSE
  443.         les     di, cs:ShareAddr        ; If function 2 (get shared memory),
  444.         .ENDIF                          ;   set ES:DI = far address
  445.  
  446. NoMultiplex LABEL  FAR                  ; Secondary entry for null Multiplex
  447.  
  448.         iret
  449.  
  450. Multiplex ENDP
  451.  
  452.  
  453. ;* CheckHotKey - Checks current keystroke for hot key. Called from Keybrd
  454. ;* handler if IBM PC/AT or compatible, or from MiscServ handler if PS/2.
  455. ;*
  456. ;* Uses:   HotScan, HotShift, HotMask, and SHFT_STAT
  457. ;*
  458. ;* Params: AL = Scan code
  459. ;*
  460. ;* Return: Carry flag set = FALSE; carry flag clear = TRUE
  461.  
  462. CheckHotKey PROC NEAR
  463.  
  464.         cmp     al, cs:HotScan          ; If current scan code isn't code
  465.         jne     e_exit                  ;   for hot key, exit with carry set
  466.  
  467.         push    es                      ; Else look into BIOS data area
  468.         sub     ax, ax                  ;   (segment 0) to check shift state
  469.         mov     es, ax
  470.         mov     al, es:[SHFT_STAT]      ; Get SHIFT-key flags
  471.         and     al, cs:HotMask          ; AND with "don't care" mask
  472.         cmp     al, cs:HotShift         ; Compare result with hot SHIFT key
  473.         pop     es
  474.         je      exit                    ; If match, exit with carry clear
  475.  
  476. e_exit: stc                             ; Set carry if not hot key
  477. exit:   ret
  478.  
  479. CheckHotKey ENDP
  480.  
  481.  
  482. ;* CheckRequest - Checks request flag and system status using the 
  483. ;* following logic:
  484. ;*
  485. ;*         IF (TsrRequestFlag AND (NOT TsrActiveFlag)
  486. ;*             AND DosStatus AND HardwareStatus)
  487. ;*             return TRUE
  488. ;*         ELSE
  489. ;*             return FALSE
  490. ;*
  491. ;* Uses:   TsrRequestFlag and TsrActiveFlag
  492. ;*
  493. ;* Params: DS = Resident code segment
  494. ;*
  495. ;* Return: Carry flag set = TRUE; carry flag clear = FALSE
  496.  
  497. CheckRequest PROC NEAR
  498.  
  499.         rol     TsrRequestFlag, 1       ; Rotate high bit into carry - set
  500.                                         ;   if TRUE (-1), clear if FALSE (0)
  501.         cmc                             ; NOT carry
  502.  
  503.         .IF     !carry?                 ; If TsrRequestFlag = TRUE:
  504.         ror     TsrActiveFlag, 1        ; Rotate low bit into carry - set
  505.                                         ;   if TRUE (-1), clear if FALSE (0)
  506.         .IF     !carry?                 ; If TsrActiveFlag = FALSE:
  507.         INVOKE  CheckDos                ; Is DOS in interruptable state?
  508.  
  509.         .IF     !carry?                 ; If so,
  510.         INVOKE  CheckHardware           ;   if hardware or BIOS unstable,
  511.         .ENDIF                          ;   set carry and exit
  512.         .ENDIF
  513.         .ENDIF
  514.         ret
  515.  
  516. CheckRequest ENDP
  517.  
  518.  
  519. ;* CheckDos - Checks status of MS-DOS using the following logic:
  520. ;*
  521. ;*         IF (NOT CritErr) AND ((NOT InDos) OR (Idle AND InDos))
  522. ;*             return DosStatus = TRUE
  523. ;*         ELSE
  524. ;*             return DosStatus = FALSE
  525. ;*
  526. ;* Uses:   CritErrAddr, InDosAddr, and intIdle
  527. ;*
  528. ;* Params: DS = Resident code segment
  529. ;*
  530. ;* Return: Carry flag set if MS-DOS is busy
  531.  
  532. CheckDos PROC   NEAR USES es bx ax
  533.  
  534.         les     bx, CritErrAddr
  535.         mov     ah, es:[bx]             ; AH = value of CritErr flag
  536.  
  537.         les     bx, InDosAddr
  538.         mov     al, es:[bx]             ; AL = value of InDos flag
  539.  
  540.         sub     bx, bx                  ; BH = 0, BL = 0
  541.         cmp     bl, intIdle.Flag        ; Carry flag set if call is from
  542.                                         ;   Interrupt 28h handler
  543.         rcl     bl, 1                   ; Rotate carry into BL: TRUE if Idle
  544.         cmp     bx, ax                  ; Carry flag clear if CritErr = 0
  545.                                         ;   and InDos <= BL
  546.         ret
  547.  
  548. CheckDos ENDP
  549.  
  550.  
  551. ;* CheckHardware - Checks status of BIOS and hardware using the
  552. ;* following logic:
  553. ;*
  554. ;*         IF HardwareActive OR KeybrdActive OR VideoActive OR DiskIOActive
  555. ;*             return HardwareStatus = FALSE
  556. ;*         ELSE
  557. ;*             return HardwareStatus = TRUE
  558. ;*
  559. ;* Uses:   intKeybrd, intVideo, and intDiskIO
  560. ;*
  561. ;* Params: DS = Resident code segment
  562. ;*
  563. ;* Return: Carry flag set if hardware or BIOS is busy
  564.  
  565. CheckHardware PROC NEAR USES ax
  566.  
  567. ; Verify hardware interrupt status by interrogating Intel 8259A
  568. ; Programmable Interrupt Controller
  569.  
  570.         mov     ax, 00001011y           ; AL = 0CW3 for Intel 8259A
  571.                                         ;   (RR = 1, RIS = 1)
  572.         out     20h, al                 ; Request 8259A in-service register
  573.         jmp     delay                   ; Wait a few cycles
  574. delay:
  575.         in      al, 20h                 ; AL = hardware interrupts being
  576.         cmp     ah, al                  ;   serviced (bit = 1 if in service)
  577.  
  578.         .IF     !carry?                 ; If no hard interrupts in service,
  579.         sub     al, al                  ;   verify BIOS interrupts not active,
  580.         cmp     al, intKeybrd.Flag      ;   check Interrupt 09 handler
  581.  
  582.         .IF     !carry?                 ; If Int 09 not active,
  583.         cmp     al, intVideo.Flag       ;   check Interrupt 10h handler
  584.  
  585.         .IF     !carry?                 ; If Int 10h not active,
  586.         cmp     al, intDiskIO.Flag      ;   check Interrupt 13h handler,
  587.         .ENDIF                          ;   return with carry set if
  588.         .ENDIF                          ;   Interrupt 09, 10h, or 13h
  589.         .ENDIF                          ;   is active
  590.  
  591.         ret
  592.  
  593. CheckHardware ENDP
  594.  
  595.  
  596. ;* Activate - Sets up for far call to TSR with the following steps:
  597. ;*
  598. ;*   1.  Stores stack pointer SS:SP and switches to new stack
  599. ;*   2.  Pushes registers onto new stack
  600. ;*   3.  Stores vectors for Interrupts 1Bh, 23h, and 23h, and
  601. ;*       replaces them with addresses of error-trapping handlers
  602. ;*   4.  Stores DOS CTRL+C checking flag, then turns off checking
  603. ;*   5.  If required, stores DTA address and switches to new DTA
  604. ;*
  605. ;* When TSR returns, restores all the above.
  606. ;*
  607. ;* Uses:   Reads or writes the following globals:
  608. ;*         OldStackAddr, TrapArray, BreakCheckFlag, TsrRequestFlag
  609. ;*
  610. ;* Params: DS = Resident code segment
  611. ;*
  612. ;* Return: None
  613.  
  614. Activate PROC   NEAR
  615.  
  616. ; Step 1.  Set up a new stack
  617.  
  618.         mov     WORD PTR OldStackAddr[0], sp    ; Save current 
  619.         mov     WORD PTR OldStackAddr[2], ss    ;   stack pointer
  620.  
  621.         cli                                     ; Turn off interrupts while
  622.         push    cs                              ;   changing stack
  623.         pop     ss                              ; New stack begins
  624.         mov     sp, OFFSET NewStack             ;   at LABEL NewStack
  625.         sti
  626.  
  627. ; Step 2.  Preserve registers (DS already saved in Clock or Idle)
  628.  
  629.         push    ax
  630.         push    bx
  631.         push    cx
  632.         push    dx
  633.         push    si
  634.         push    di
  635.         push    bp
  636.         push    es
  637.  
  638.         cld                                     ; Clear direction flag
  639.  
  640. ; Step 3.  Set up trapping handlers for keyboard breaks and DOS
  641. ; critical errors (Interrupts 1Bh, 23h, and 24h)
  642.  
  643.         mov     cx, CTRAP                       ; CX = number of handlers
  644.         mov     si, OFFSET TrapArray            ; DS:SI points to trap array
  645.  
  646.         .REPEAT
  647.         mov     al, [si]                        ; AL = interrupt number
  648.         mov     ah, 35h                         ; Request DOS Function 35h
  649.         int     21h                             ; Get interrupt vector (ES:BX)
  650.         mov     WORD PTR [si].INTR.OldHand[0], bx ; Save far address of
  651.         mov     WORD PTR [si].INTR.OldHand[2], es ;   application's handler
  652.         mov     dx, WORD PTR [si].INTR.NewHand[0] ; DS:DX points to TSR's hand
  653.         mov     ah, 25h                         ; Request DOS Function 25h
  654.         int     21h                             ; Set interrupt vector
  655.         add     si, SIZEOF INTR                 ; DS:SI points to next in list
  656.         .UNTILCXZ
  657.  
  658. ; Step 4.  Disable MS-DOS break checking during disk I/O
  659.  
  660.         mov     ax, 3300h               ; Request DOS Function 33h
  661.         int     21h                     ; Get CTRL+BREAK flag in DL
  662.         mov     BreakCheckFlag, dl      ; Preserve it
  663.  
  664.         sub     dl, dl                  ; DL = 0 to disable I/O break checking
  665.         mov     ax, 3301h               ; Request DOS Function 33h
  666.         int     21h                     ; Set CTRL+BREAK flag from DL
  667.  
  668. ; Step 5.  If TSR requires a disk transfer area, store address of current
  669. ; DTA and switch buffer address to this segment. See Section 19.5.3 of
  670. ; Programmer's Guide for more information about the DTA. 
  671.  
  672.         IFDEF   DTA_SIZ
  673.         mov     ah, 2Fh                         ; Request DOS Function 2Fh
  674.         int     21h                             ; Get DTA Address into ES:BX
  675.         mov     WORD PTR OldDtaAddr[0], bx      ; Store address
  676.         mov     WORD PTR OldDtaAddr[2], es
  677.  
  678.         mov     dx, OFFSET DtaBuff              ; DS:DX points to new DTA
  679.         mov     ah, 1Ah                         ; Request DOS Function 1Ah
  680.         int     21h                             ; Set DTA Address
  681.         ENDIF
  682.  
  683. ; Call main body of TSR.
  684.  
  685.         mov     ax, @data
  686.         mov     ds, ax                          ; Initialize DS and ES
  687.         mov     es, ax                          ;   to data segment
  688.  
  689.         call    cs:TsrAddr                      ; Call main part of TSR
  690.  
  691.         push    cs
  692.         pop     ds                              ; Reset DS to this segment
  693.  
  694. ; Undo step 5.  Restore previous DTA (if required)
  695.  
  696.         IFDEF   DTA_SIZ
  697.         push    ds                      ; Preserve DS
  698.         lds     dx, OldDtaAddr          ; DS:DX points to application's DTA
  699.         mov     ah, 1Ah                 ; Request DOS Function 1Ah
  700.         int     21h                     ; Set DTA Address
  701.         pop     ds
  702.         ENDIF
  703.  
  704. ; Undo step 4.  Restore previous MS-DOS break checking
  705.  
  706.         mov     dl, BreakCheckFlag      ; DL = previous break state
  707.         mov     ax, 3301h               ; Request DOS Function 33h
  708.         int     21h                     ; Set CTRL+BREAK flag from DL
  709.  
  710. ; Undo step 3.  Restore previous vectors for error-trapping handlers
  711.  
  712.         mov     cx, CTRAP
  713.         mov     di, OFFSET TrapArray
  714.         push    ds                      ; Preserve DS
  715.         push    ds                      ; ES = resident code segment
  716.         pop     es
  717.  
  718.         .REPEAT
  719.         mov     al, es:[di]             ; AL = interrupt number
  720.         lds     dx, es:[di].INTR.OldHand; DS:DX points to application's handler
  721.         mov     ah, 25h                 ; Request DOS Function 25h
  722.         int     21h                     ; Set interrupt vector from DS:DX
  723.         add     di, SIZEOF INTR         ; ES:DI points to next in list
  724.         .UNTILCXZ
  725.         pop     ds
  726.  
  727. ; Undo step 2.  Restore registers from stack
  728.  
  729.         pop     es
  730.         pop     bp
  731.         pop     di
  732.         pop     si
  733.         pop     dx
  734.         pop     cx
  735.         pop     bx
  736.         pop     ax
  737.  
  738. ; Undo step 1.  Restore address of original stack to SS:SP
  739.  
  740.         cli
  741.         mov     sp, WORD PTR OldStackAddr[0]
  742.         mov     ss, WORD PTR OldStackAddr[2]
  743.         sti
  744.  
  745. ; Clear request flag and return to caller (Clock or Idle procedure)
  746.  
  747.         mov     TsrRequestFlag, FALSE
  748.         ret
  749.  
  750. Activate ENDP
  751.  
  752.  
  753.  
  754. ;* INSTALLATION SECTION - The following code is executed only during
  755. ;* the TSR's installation phase. When the program terminates through
  756. ;* Function 31h, the above code and data remain resident; memory
  757. ;* occupied by the following code segment is returned to the operating
  758. ;* system.
  759.  
  760. DGROUP  GROUP INSTALLCODE
  761.  
  762. INSTALLCODE SEGMENT PARA PUBLIC 'I_CODE'
  763.         ASSUME  ds:@code
  764.  
  765. ;* Install - Prepares for installation of a TSR by chaining interrupt
  766. ;* handlers and initializing pointers to DOS flags. Install does not
  767. ;* call the Terminate-and-Stay-Resident function.
  768. ;*
  769. ;* This library of routines accommodates both keyboard-activated and
  770. ;* time-activated TSRs. The latter are TSRs that activate at a preset
  771. ;* time. If the first parameter (Param1) is a valid scan code, Install
  772. ;* assumes the TSR is activated from the keyboard and sets up a keyboard
  773. ;* handler to search for the hot key. If Param1 is null, Install assumes
  774. ;* the next two parameters (Param2 and Param3) are respectively the hour
  775. ;* and minute at which the TSR is to activate. In this case, Install 
  776. ;* calls GetTimeToElapse to initialize the variable CountDown and sets
  777. ;* up KeybrdMonitor as the keyboard handler. CountDown and the secondary
  778. ;* counter Tick91 serve here the same functions as they do for the
  779. ;* ALARM.ASM program presented in Section 19.3 of the Programmer's Guide.
  780. ;* Install is callable from a high-level language.
  781. ;*
  782. ;* Uses:   InDosAddr, CritErrAddr, CHAND,
  783. ;*         HandArray, CTRAP, TrapArray
  784. ;*
  785. ;*                  Keyboard-activated                 Time-activated
  786. ;*                  ------------------                 --------------
  787. ;* Params: Param1 - Scan code for hot key              0
  788. ;*         Param2 - Bit value for shift hot key        Hour to activate
  789. ;*         Param3 - Bit mask for shift hot key         Minute to activate
  790. ;*         Param4 - Far address of main TSR procedure  (same)
  791. ;*
  792. ;* Return: AX = 0 if successful, or one of the following codes:
  793. ;*         IS_INSTALLED           FLAGS_NOT_FOUND        NO_IDNUM
  794. ;*         ALREADY_INSTALLED      WRONG_DOS
  795.  
  796. Install PROC    FAR USES ds si di,
  797.         Param1:WORD, Param2:WORD, Param3:WORD, Param4:FAR PTR FAR
  798.  
  799.         mov     ax, @code
  800.         mov     ds, ax                          ; Point DS to code segment
  801.  
  802. ; Get and store parameters passed from main program module
  803.  
  804.         mov     al, BYTE PTR Param1
  805.         mov     HotScan, al                     ; Store hot-key scan code
  806.         mov     al, BYTE PTR Param2             ;   or flag for time activate
  807.         mov     HotShift, al                    ; Store hot-key shift value
  808.         mov     al, BYTE PTR Param3             ;   or hour value
  809.         mov     HotMask, al                     ; Store hot-key shift mask
  810.                                                 ;   or minute value
  811.         mov     ax, WORD PTR Param4[0]
  812.         mov     bx, WORD PTR Param4[2]
  813.         mov     WORD PTR TsrAddr[0], ax         ; Store segment:offset of
  814.         mov     WORD PTR TsrAddr[2], bx         ;   TSR's main code
  815.  
  816. ; Get addresses of DOS flags, then check for prior installation
  817.  
  818.         INVOKE  GetDosFlags             ; Find DOS service flags
  819.         or      ax, ax
  820.         jnz     exit                    ; If flags not found, quit
  821.  
  822.         sub     al, al                  ; Request multiplex function 0
  823.         call    CallMultiplex           ; Invoke Interrupt 2Fh
  824.         cmp     ax, NOT_INSTALLED       ; Check for presence of resident TSR
  825.  
  826.         .IF     !zero?                  ; If TSR is installed,
  827.         cmp     ax, IS_INSTALLED        ;   return with appropriate
  828.         jne     exit                    ;   error code
  829.         mov     ax, ALREADY_INSTALLED
  830.         jmp     exit
  831.         .ENDIF
  832.  
  833. ; Check if TSR is to activate at the hour:minute specified by Param2:Param3.
  834. ; If so, determine the number of 5-second intervals that must elapse before
  835. ; activation, then set up the code at the far LABEL KeybrdMonitor to serve
  836. ; as the keyboard handler.
  837.  
  838.         .IF     HotScan == 0            ; If valid scan code given,
  839.         mov     ah, HotShift            ;   AH = hour to activate
  840.         mov     al, HotMask             ;   AL = minute to activate
  841.         call    GetTimeToElapse         ; Get number of 5-second intervals
  842.         mov     CountDown, ax           ;   to elapse before activation
  843.  
  844.         .ELSE                           ; Force use of KeybrdMonitor as
  845.                                         ;   keyboard handler
  846.         cmp     Version, 031Eh          ; DOS Version 3.3 or higher?
  847.         jb      setup                   ; No?  Skip next step
  848.  
  849. ; Test for IBM PS/2 series. If not PS/2, use Keybrd and SkipMiscServ as
  850. ; handlers for Interrupts 09 and 15h respectively. If PS/2 system, set up
  851. ; KeybrdMonitor as the Interrupt 09 handler. Audit keystrokes with MiscServ
  852. ; handler, which searches for the hot key by handling calls to Interrupt 15h
  853. ; (Miscellaneous System Services). Refer to Section 19.2.1 of the Programmer's
  854. ; Guide for more information about keyboard handlers.
  855.  
  856.         mov     ax, 0C00h               ; Function 0Ch (Get System
  857.         int     15h                     ;   Configuration Parameters)
  858.         sti                             ; Compaq ROM may leave interrupts
  859.                                         ;   disabled
  860.  
  861.         jc      setup                   ; If carry set,
  862.         or      ah, ah                  ;   or if AH not 0,
  863.         jnz     setup                   ;   services are not supported
  864.  
  865.         test    BYTE PTR es:[bx+5], 00010000y   ; Test bit 4 to see if
  866.         jz      setup                           ;   intercept is implemented
  867.  
  868.         mov     ax, OFFSET MiscServ             ; If so, set up MiscServ as
  869.         mov     WORD PTR intMisc.NewHand, ax    ;   Interrupt 15h handler
  870.         .ENDIF
  871.  
  872.         mov     ax, OFFSET KeybrdMonitor        ; Set up KeybrdMonitor as
  873.         mov     WORD PTR intKeybrd.NewHand, ax  ;   Interrupt 09 handler
  874.  
  875. ; Interrupt structure is now initialized for either PC/AT or PS/2 system.
  876. ; Get existing handler addresses from interrupt vector table, store in
  877. ; OldHand member, and replace with addresses of new handlers.
  878.  
  879. setup:
  880.         mov     cx, CHAND               ; CX = count of handlers
  881.         mov     si, OFFSET HandArray    ; SI = offset of handler structures
  882.  
  883.         .REPEAT
  884.         mov     ah, 35h                 ; Request DOS Function 35h
  885.         mov     al, [si]                ; AL = interrupt number
  886.         int     21h                     ; Get interrupt vector in ES:BX
  887.         mov     WORD PTR [si].INTR.OldHand[0], bx ; Save far address
  888.         mov     WORD PTR [si].INTR.OldHand[2], es ;  of current handler
  889.         mov     dx, WORD PTR [si].INTR.NewHand[0] ; DS:DX points to TSR handler
  890.         mov     ah, 25h                 ; Request DOS Function 25h
  891.         int     21h                     ; Set interrupt vector from DS:DX
  892.         add     si, SIZEOF INTR         ; DS:SI points to next in list
  893.         .UNTILCXZ
  894.  
  895.         sub     ax, ax                  ; Clear return code
  896. exit:
  897.         ret                             ; Return to caller
  898.  
  899. Install ENDP
  900.  
  901.  
  902. ;* Deinstall - Prepares for deinstallation of a TSR. Deinstall is the
  903. ;* complement of the Install procedure. It restores to the vector table
  904. ;* the original addresses replaced during installation, thus unhooking
  905. ;* the TSR's handlers. Checks to see if another TSR has installed handlers
  906. ;* for the interrupts in array HandArray. If so, the procedure fails with
  907. ;* an appropriate error code. Callable from a high-level language.
  908. ;*
  909. ;* Params: None
  910. ;*
  911. ;* Return: AX = Segment address of resident portion's PSP or 
  912. ;*              one of the following error codes:
  913. ;*              CANT_DEINSTALL           WRONG_DOS
  914.                 
  915. Deinstall PROC  FAR USES ds si di
  916.  
  917.         mov     ax, @code
  918.         mov     ds, ax                  ; Point DS to code segment
  919.  
  920.         sub     al, al                  ; Request multiplex function 0
  921.         call    CallMultiplex           ; Get resident code segment in ES
  922.  
  923.         cmp     ax, IS_INSTALLED        ; If not resident,
  924.         jne     exit                    ;   exit with error
  925.         push    es                      ; Else point DS to
  926.         pop     ds                      ;   resident code segment
  927.         mov     cx, CHAND               ; Count of handlers
  928.         mov     si, OFFSET HandArray    ; SI points to handler structures
  929.  
  930. ; Read current vectors for TSR's interrupt handlers and compare with far
  931. ; addresses. If mismatch, another TSR has installed new handlers and ours
  932. ; cannot be safely deinstalled.
  933.  
  934.         .REPEAT
  935.         mov     al, [si]                ; AL = interrupt number
  936.         mov     ah, 35h                 ; Request DOS Function 35h
  937.         int     21h                     ; Get interrupt vector in ES:BX
  938.         cmp     bx, WORD PTR [si].INTR.NewHand[0] ; If offset different,
  939.         jne     e_exit                            ;   error
  940.         mov     ax, es
  941.         cmp     ax, WORD PTR [si].INTR.NewHand[2] ; If segment different,
  942.         jne     e_exit                            ;   error
  943.         add     si, SIZEOF INTR         ; DS:SI points to next in list
  944.         .UNTILCXZ
  945.  
  946. ; If no interrupts replaced, call TSR's multiplex handler to locate
  947. ; address of resident portion's PSP. Although the PSP is not required
  948. ; until memory is returned to DOS, the call must be done now before
  949. ; unhooking the multiplex handler.
  950.  
  951.         mov     al, 1                   ; Request multiplex function 1
  952.         call    CallMultiplex           ; Get resident code's PSP in ES
  953.         push    es                      ; Save it
  954.  
  955. ; Unhook all handlers by restoring the original vectors to vector table.
  956.  
  957.         mov     cx, CHAND               ; Count of installed handlers
  958.         mov     si, OFFSET HandArray    ; SI points to handler structures
  959.  
  960.         .REPEAT
  961.         mov     al, [si]                ; AL = interrupt number
  962.         push    ds                      ; Preserve DS segment
  963.         lds     dx, [si].INTR.OldHand   ; Put vector in DS:DX
  964.         mov     ah, 25h                 ; Request DOS Function 25h
  965.         int     21h                     ; Set interrupt vector from DS:DX
  966.         pop     ds
  967.         add     si, SIZEOF INTR         ; DS:SI points to next in list
  968.         .UNTILCXZ
  969.  
  970.         pop     ax                      ; Return address of resident PSP
  971.         jmp     exit                    ;  to signal success
  972. e_exit:
  973.         mov     ax, CANT_DEINSTALL
  974. exit:
  975.         ret
  976.  
  977. Deinstall ENDP
  978.  
  979.  
  980. ;* GetVersion - Gets the DOS version and stores it in a global variable as
  981. ;* well as returning it in AX.
  982. ;*
  983. ;* Uses:   Version
  984. ;*
  985. ;* Params: DS = Resident code segment
  986. ;*
  987. ;* Return: AH = Major version
  988. ;*         AL = Minor version
  989.  
  990. GetVersion PROC NEAR
  991.  
  992.         mov     ax, 3000h               ; Request DOS Function 30h
  993.         int     21h                     ; Get MS-DOS version number
  994.         .IF     al < 2                  ; If Version 1.x:
  995.         mov     ax, WRONG_DOS           ; Abort with WRONG_DOS as error code
  996.         .ELSE
  997.         xchg    ah, al                  ; AH = major, AL = minor version
  998.         mov     Version, ax             ; Save in global
  999.         .ENDIF
  1000.         ret
  1001.  
  1002. GetVersion ENDP
  1003.  
  1004.  
  1005. ;* GetDosFlags - Gets pointers to the DOS InDos and Critical Error flags.
  1006. ;*
  1007. ;* Params: DS = Resident code segment
  1008. ;*
  1009. ;* Return: 0 if successful, or the following error code:
  1010. ;*         FLAGS_NOT_FOUND
  1011.  
  1012. GetDosFlags PROC NEAR
  1013.  
  1014. ; Get InDOS address from MS-DOS
  1015.  
  1016.         mov     ah, 34h                         ; Request DOS Function 34h
  1017.         int     21h                             ; Get Address of InDos flag
  1018.         mov     WORD PTR InDosAddr[0], bx       ; Store address (ES:BX)
  1019.         mov     WORD PTR InDosAddr[2], es       ;   for later access
  1020.  
  1021. ; Determine address of Critical Error Flag
  1022.  
  1023.         mov     ax, Version             ; AX = DOS version number
  1024.  
  1025. ; If DOS 3.1 or greater and not OS/2 compatibility mode, Critical Error
  1026. ; flag is in byte preceding InDOS flag 
  1027.         .IF     (ah < 10) && (ah >= 3) && (al >= 10)
  1028.         dec     bx                      ; BX points to byte before InDos flag
  1029.  
  1030.         .ELSE
  1031. ; For earlier versions, the only reliable method is to scan through
  1032. ; DOS to find an INT 28h instruction in a specific context.
  1033.  
  1034.         mov     cx, 0FFFFh              ; Maximum bytes to scan
  1035.         sub     di, di                  ; ES:DI = start of DOS segment
  1036.  
  1037. INT_28  EQU     028CDh
  1038.  
  1039.         .REPEAT
  1040.         mov     ax, INT_28              ; Load opcode for INT 28h
  1041.  
  1042.         .REPEAT
  1043.         repne   scasb                   ; Scan for first byte of opcode
  1044.  
  1045.         .IF     !zero?
  1046.         mov     ax, FLAGS_NOT_FOUND     ; Return error if not found
  1047.         jmp     exit
  1048.         .ENDIF
  1049.         .UNTIL  ah == es:[di]           ; For each matching first byte,
  1050.                                         ;   check the second byte until match
  1051.  
  1052. ; See if INT 28h is in this context:
  1053. ;                                       ;     (-7)    (-5)
  1054. ;       CMP     ss:[CritErrFlag], 0     ;  36, 80, 3E,  ?,  ?,  0
  1055. ;       JNE     NearLabel               ;  75,  ?
  1056.         int     28h                     ;  CD, 28
  1057. ;                                       ;  (0) (1)
  1058. CMP_SS    EQU   3E80h
  1059. P_CMP_SS  EQU   8
  1060. P_CMP_OP  EQU   6
  1061.  
  1062.         mov     ax, CMP_SS              ; Load and compare opcode to CMP
  1063.         .IF     ax == es:[di-P_CMP_SS]  ; If match:
  1064.         mov     bx, es:[di-P_CMP_OP]    ; BX = offset of
  1065.         jmp     exit                    ;   Critical Error flag
  1066.         .ENDIF
  1067.  
  1068. ; See if INT 28h is in this context:
  1069. ;                                       ;     (-12)   (-10)
  1070. ;       TEST    ?s:[CritErr], 0FFh      ;  ?6  F6, 06,  ?,  ?, FF
  1071. ;       JNE     NearLabel               ;  75, ?
  1072. ;       PUSH    ss:[CritErrFlag]        ;  36, FF, 36,  ?,  ?
  1073.         int     28h                     ;  CD, 28
  1074. ;                                       ;  (0) (1)
  1075. TEST_SS   EQU   06F6h
  1076. P_TEST_SS EQU   13
  1077. P_TEST_OP EQU   11
  1078.  
  1079.         mov     ax, TEST_SS             ; Load AX = opcode for TEST
  1080.         .UNTIL  ax == es:[di-P_TEST_SS] ; If not TEST, continue scan
  1081.  
  1082.         mov     bx, es:[di-P_TEST_OP]   ; Else load BX with offset of
  1083.         .ENDIF                          ;   Critical Error flag
  1084. exit:
  1085.         mov     WORD PTR CritErrAddr[0], bx     ; Store address of
  1086.         mov     WORD PTR CritErrAddr[2], es     ;   Critical Error flag
  1087.         sub     ax, ax                          ; Clear error code
  1088.         ret
  1089.  
  1090. GetDosFlags ENDP
  1091.  
  1092.  
  1093. ;* GetTimeToElapse - Determines number of 5-second intervals that
  1094. ;* must elapse between specified hour:minute and current time.
  1095. ;*
  1096. ;* Params: AH = Hour
  1097. ;*         AL = Minute
  1098. ;*
  1099. ;* Return: AX = Number of 5-second intervals
  1100.  
  1101. GetTimeToElapse PROC NEAR 
  1102.  
  1103.         push    ax                      ; Save hour:minute
  1104.         mov     ah, 2Ch                 ; Request DOS Function 2Ch
  1105.         int     21h                     ; Get Time (CH:CL = hour:minute)
  1106.         pop     bx                      ; Recover hour:minute
  1107.         mov     dl, dh
  1108.         sub     dh, dh
  1109.         push    dx                      ; Save DX = current seconds
  1110.  
  1111.         mov     al, 60                  ; 60 minutes/hour
  1112.         mul     bh                      ; Multiply by specified hour
  1113.         sub     bh, bh
  1114.         add     bx, ax                  ; BX = minutes from midnight
  1115.                                         ;   to activation time
  1116.         mov     al, 60                  ; 60 minutes/hour
  1117.         mul     ch                      ; Multiply by current hour
  1118.         sub     ch, ch
  1119.         add     ax, cx                  ; AX = minutes from midnight
  1120.                                         ;   to current time
  1121.         sub     bx, ax                  ; BX = minutes to elapse before
  1122.         .IF     carry?                  ; If activation is tomorrow,
  1123.         add     bx, 24 * 60             ;   add number of minutes per day
  1124.         .ENDIF
  1125.  
  1126.         mov     ax, 60
  1127.         mul     bx                      ; DX:AX = minutes-to-elapse-times-60
  1128.         pop     bx                      ; Recover current seconds
  1129.         sub     ax, bx                  ; DX:AX = seconds to elapse before
  1130.         sbb     dx, 0                   ;   activation
  1131.         .IF     carry?                  ; If negative,
  1132.         mov     ax, 5                   ;   assume 5 seconds
  1133.         cwd
  1134.         .ENDIF
  1135.  
  1136.         mov     bx, 5                   ; Divide result by 5 seconds
  1137.         div     bx                      ; AX = number of 5-second intervals
  1138.         ret
  1139.  
  1140. GetTimeToElapse ENDP
  1141.  
  1142.  
  1143. ;* CallMultiplex - Calls the Multiplex Interrupt (Interrupt 2Fh).
  1144. ;*
  1145. ;* Uses:   IDstring
  1146. ;*
  1147. ;* Params: AL = Function number for multiplex handler
  1148. ;*
  1149. ;* Return: AX    = One of the following return codes:
  1150. ;*                 NOT_INSTALLED      IS_INSTALLED       NO_IDNUM
  1151. ;*         ES:DI = Resident code segment:identifier string (function 0)
  1152. ;*         ES:DI = Resident PSP segment address (function 1)
  1153. ;*         ES:DI = Far address of shared memory (function 2)
  1154.  
  1155. CallMultiplex PROC FAR USES ds
  1156.  
  1157.         push    ax                      ; Save function number
  1158.         mov     ax, @code
  1159.         mov     ds, ax                  ; Point DS to code segment
  1160.  
  1161. ; First, check 2Fh vector. DOS Version 2.x may leave the vector null
  1162. ; if PRINT.COM is not installed. If vector is null, point it to IRET
  1163. ; instruction at LABEL NoMultiplex. This allows the new multiplex
  1164. ; handler to pass control, if necessary, to a proper existing routine.
  1165.  
  1166.         mov     ax, 352Fh               ; Request DOS Function 35h
  1167.         int     21h                     ; Get interrupt vector in ES:BX
  1168.         mov     ax, es
  1169.         or      ax, bx
  1170.         .IF     zero?                   ; If Null vector,
  1171.         mov     dx, OFFSET NoMultiplex  ;   set vector to IRET instruction
  1172.         mov     ax, 252Fh               ;   at LABEL NoMultiplex
  1173.         int     21h                     ; Set interrupt vector
  1174.         .ENDIF
  1175.  
  1176. ; Second, call Interrupt 2Fh with function 0 (presence request). Cycle
  1177. ; through allowable identity numbers (192 to 255) until TSR's multiplex
  1178. ; handler returns ES:DI = IDstring to verify its presence or until call
  1179. ; returns AL = 0, indicating the TSR is not installed.
  1180.  
  1181.         mov     dh, 192                 ; Start with identity number = 192
  1182.  
  1183.         .REPEAT
  1184.         mov     ah, dh                  ; Call Multiplex with AH = trial ID
  1185.         sub     al, al                  ;   and AL = function 0
  1186.         push    dx                      ; Save DH and DS in case call
  1187.         push    ds                      ;   destroys them
  1188.         int     2Fh                     ; Multiplex
  1189.         pop     ds                      ; Recover DS and
  1190.         pop     dx                      ;   current ID number in DH
  1191.         or      al, al                  ; Does a handler claim this ID number?
  1192.         jz      no                      ; If not, stop search
  1193.  
  1194.         .IF     al == 0FFh              ; If handler ready to process calls,
  1195.         mov     si, OFFSET IDstring     ;   point DS:SI to ID string, compare
  1196.         mov     cx, IDstrlen            ;   with string at ES:DI returned
  1197.         repe    cmpsb                   ;   by multiplex handler
  1198.         je      yes                     ; If equal, TSR's handler is found
  1199.         .ENDIF
  1200.  
  1201.         inc     dh                      ; This handler is not the one
  1202.         .UNTIL  zero?                   ; Try next identity number up to 255
  1203.  
  1204.         mov     ax, NO_IDNUM            ; In the unlikely event that numbers
  1205.         jmp     e_exit                  ;   192-255 are all taken, quit
  1206.  
  1207. ; Third, assuming handler is found and verified, process the multiplex
  1208. ; call with the requested function number.
  1209.  
  1210. yes:
  1211.         pop     ax                      ; AL = original function number
  1212.         mov     ah, dh                  ; AH = identity number
  1213.         int     2Fh                     ; Multiplex
  1214.         mov     ax, IS_INSTALLED        ; Signal that handler has been found
  1215.         jmp     exit                    ;   and quit
  1216.  
  1217. ; Reaching this section means multiplex handler (and TSR) not installed.
  1218. ; Since the value in DH is not claimed by any handler, it will be used as
  1219. ; the resident TSR's identity number.  Save the number in resident code
  1220. ; segment so multiplex handler can find it.
  1221.  
  1222. no:
  1223.         mov     IDnumber, dh            ; Save multiplex identity number
  1224.         mov     ax, NOT_INSTALLED       ; Signal handler is not installed
  1225. e_exit:
  1226.         pop     bx                      ; Remove function number from stack
  1227. exit:
  1228.         ret
  1229.  
  1230. CallMultiplex ENDP
  1231.  
  1232.  
  1233. ;* InitTsr - Initializes DOS version variables and multiplex data with
  1234. ;* following parameters. This procedure must execute before calling
  1235. ;* either the Install, Deinstall, or CallMultiplex procedures. Callable
  1236. ;* from a high-level language.
  1237. ;*
  1238. ;* Uses:   IDstring
  1239. ;*
  1240. ;* Params: PspParam - Segment address of PSP
  1241. ;*         StrParam - Far address of TSR's identifier string
  1242. ;*         ShrParam - Far address of shared memory
  1243. ;*
  1244. ;* Return: AX = WRONG_DOS if not DOS Version 2.0 or higher
  1245.  
  1246. InitTsr PROC    FAR USES ds es si di,
  1247.         PspParam:WORD, StrParam:FPVOID, ShrParam:FPVOID
  1248.  
  1249.         mov     ax, @code
  1250.         mov     ds, ax                          ; Point DS and ES
  1251.         mov     es, ax                          ;   to code segment
  1252.  
  1253. ; Get and store parameters passed from main program module
  1254.  
  1255.         mov     ax, PspParam
  1256.         mov     TsrPspSeg, ax                   ; Store PSP segment address
  1257.  
  1258.         mov     ax, WORD PTR ShrParam[0]
  1259.         mov     bx, WORD PTR ShrParam[2]
  1260.         mov     WORD PTR ShareAddr[0], ax       ; Store far address of
  1261.         mov     WORD PTR ShareAddr[2], bx       ;   shared memory
  1262.  
  1263.         push    ds
  1264.         mov     si, WORD PTR StrParam[0]        ; DS:SI points to multiplex
  1265.         mov     ax, WORD PTR StrParam[2]        ;   identifier string
  1266.         mov     ds, ax
  1267.         mov     di, OFFSET IDstring             ; Copy string to IDstring
  1268.         mov     cx, STR_LEN                     ;   at ES:DI so multiplex
  1269.                                                 ;   handler has a copy
  1270.         .REPEAT
  1271.         lodsb                                   ; Copy STR_LEN characters
  1272.         .BREAK .IF al == 0                      ;   or until null terminator
  1273.         stosb                                   ;   found
  1274.         .UNTILCXZ
  1275.  
  1276.         pop     ds                              ; Recover DS = code segment
  1277.         mov     ax, STR_LEN
  1278.         sub     ax, cx
  1279.         mov     IDstrlen, ax                    ; Store string length
  1280.  
  1281.         INVOKE  GetVersion                      ; Return AX = version number
  1282.         ret                                     ;   or WRONG_DOS
  1283.  
  1284. InitTsr ENDP
  1285.  
  1286.  
  1287. INSTALLCODE ENDS
  1288.  
  1289.         END
  1290.